package com.jprocms.module.cms.controller.admin.sitetpl;

import cn.hutool.core.collection.CollUtil;
import com.jpro.framework.common.exception.ServerException;
import com.jpro.framework.common.exception.ServiceException;
import com.jpro.framework.common.pojo.CommonResult;
import com.jpro.framework.common.util.collection.CollectionUtils;
import com.jpro.framework.security.core.util.SecurityFrameworkUtils;
import com.jprocms.module.cms.constant.CmsSysConstants;
import com.jprocms.module.cms.controller.admin.sitetpl.dto.TplDeleteDto;
import com.jprocms.module.cms.controller.admin.sitetpl.dto.TplReSourceDto;
import com.jprocms.module.cms.controller.admin.sitetpl.dto.TplRenameDto;
import com.jprocms.module.cms.controller.admin.sitetpl.dto.TplSaveDirDto;
import com.jprocms.module.cms.controller.admin.sitetpl.vo.SiteTplListVo;
import com.jprocms.module.cms.controller.admin.sitetpl.vo.SiteTplTreeVo;
import com.jprocms.module.cms.controller.admin.tplinclude.vo.TplIncludeSaveReqVO;
import com.jprocms.module.cms.convert.sitetpl.SiteTplConvert;
import com.jprocms.module.cms.dal.dataobject.model.ModelDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.dal.dataobject.tplinclude.TplIncludeDO;
import com.jprocms.module.cms.enums.EnumModelType;
import com.jprocms.module.cms.framework.annotation.DecodeBase64Anno;
import com.jprocms.module.cms.framework.file.Zipper;
import com.jprocms.module.cms.service.model.ModelService;
import com.jprocms.module.cms.service.site.SiteService;
import com.jprocms.module.cms.service.sitetpl.IResFile;
import com.jprocms.module.cms.service.sitetpl.TplService;
import com.jprocms.module.cms.service.tplinclude.TplIncludeService;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.FileUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jpro.framework.common.pojo.CommonResult.success;
import static com.jprocms.module.cms.constant.CmsSysConstants.*;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;

/**
 * @author jprocms
 */
@Tag(name = "管理后台 - 站点模板管理")
@RestController
@RequestMapping("/cms/tpl")
@Validated
public class TemplateController extends AbstractTplAndResourceController {

    private static final Logger log = LoggerFactory.getLogger(TemplateController.class);

    @GetMapping(value = "/tree")
    @Operation(summary = "左侧模板树")
    @PreAuthorize("@ss.hasPermission('cms:tpl:list')")
    public CommonResult<List<SiteTplTreeVo>> tree(String root) throws ServerException, IOException {
        SiteDO site = CmsContextUtil.getSite();
        //模板路径
        String tplPath = site.getTplPath();
        if (StringUtils.isBlank(root)) {
            root = tplPath;
        } else {
            FileUtil.isValidFilename(tplPath, root);
        }
        //顶层文件夹
        List<IResFile> list = tplService.getChild(root);
        if (list == null || list.isEmpty()) {
            return success(Collections.emptyList());
        }
        list = list.stream().sorted(Comparator.comparing(IResFile::getFilename)).collect(Collectors.toList());
        List<SiteTplTreeVo> vos = CollUtil.newArrayList();
        //转换得到所有子文件夹
        SiteTplConvert.INSTANCE.convertTplResTree(vos, list);
        List<SiteTplTreeVo> emptyList = Collections.emptyList();
        for (SiteTplTreeVo vo : vos) {
            vo.setChildren(emptyList);
        }
        List<SiteTplTreeVo> voList = CollUtil.newArrayList();
        voList.addAll(vos);
        return success(voList);
    }

    /**
     * 模板列表
     *
     * @param root 路径
     * @return ResponseInfo
     */
    @SuppressWarnings("unchecked")
    @GetMapping(value = "/list")
    @Operation(summary = "模板列表")
    @PreAuthorize("@ss.hasPermission('cms:tpl:list')")
    public CommonResult<List<SiteTplListVo>> list(String root, String filename) throws ServerException, IOException {
        SiteDO site = CmsContextUtil.getSite();
        //模板路径
        String tplPath = site.getTplPath();
        if (StringUtils.isBlank(root)) {
            root = tplPath;
        } else {
            FileUtil.isValidFilename(tplPath, root);
        }
        List<IResFile> list = tplService.getChild(root);
        if (list == null || list.isEmpty()) {
            return success(Collections.emptyList());
        }
        if (StringUtils.isNotBlank(filename)) {
            list = list.stream().filter(x -> x.getFilename().contains(filename)).collect(Collectors.toList());
        }
        return success(SiteTplConvert.INSTANCE.convertTplResList(list));
    }

    /**
     * 获取文内容
     *
     * @param name 文件名
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @GetMapping(value = "/detail")
    @Operation(summary = "模板详情")
    public CommonResult<IResFile> get(String name) throws ServerException, IOException {
        SiteDO site = CmsContextUtil.getSite();
        validBlank(name);
        validFileExist(name, site.getTplPath());
        IResFile file = tplService.get(name);
        return success(file);
    }

    /**
     * 添加模板
     *
     * @param dto    路径
     * @param result BindingResult
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @DecodeBase64Anno
    @Operation(summary = "模板保存")
    @PreAuthorize("@ss.hasPermission('cms:tpl:save')")
    @PostMapping(value = "/save")
    public CommonResult<Boolean> save(@Valid @RequestBody TplReSourceDto dto, BindingResult result) throws ServerException, IOException {
        validateBindingResult(result);
        SiteDO site = CmsContextUtil.getSite();
        String root = dto.getRoot();
        if (StringUtils.isBlank(root)) {
            root = site.getTplPath();
        }
        String name = root + "/" + dto.getFilename() + TPL_SUFFIX;
        validSave(name, site.getTplPath());
        dto.setRoot(root);
        tplService.saveFile(dto);
        saveOrUpdateTplInclude(dto, site);
        return success(true);
    }

    /**
     * 修改模板
     *
     * @param dto     模板资源Dto
     * @param request HttpServletRequest
     * @param result  BindingResult
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @DecodeBase64Anno
    @Operation(summary = "模板修改")
    @PreAuthorize("@ss.hasPermission('cms:tpl:update')")
    @PostMapping(value = "/update")
    public CommonResult<Boolean> update(@Valid @RequestBody TplReSourceDto dto, HttpServletRequest request,
                                        BindingResult result) throws Exception {
        validateBindingResult(result);
        SiteDO site = CmsContextUtil.getSite();
        String oldName = dto.getRoot() + SPT + dto.getOriginFileName();
        String newName = dto.getRoot() + SPT + dto.getFilename();
        if (newName.equalsIgnoreCase(oldName)) {
            //新名称和旧名称一样，只校验原文件是否存在
            validFileExist(oldName, site.getTplPath());
        } else {
            //新名称和旧名称不一样，校验原文件是否存在及新文件是否存在
            validReName(oldName, newName, site.getTplPath());
        }
        //拼接上相对路径 统一service调用 模板和资源可以共用一个方法
        dto.setOriginFileName(oldName);
        dto.setFilename(newName);
        tplService.update(dto);
        saveOrUpdateTplInclude(dto, site);
        return success(true);
    }

    private void saveOrUpdateTplInclude(TplReSourceDto dto, SiteDO site) {
        final TplIncludeDO tplInclude = tplIncludeService.getTplInclude(dto.getFilename(), site.getId());
        if (dto.getIncludePage()) {
            //是包含页面
            if (tplInclude == null) {
                TplIncludeSaveReqVO tplIncludeSaveReqVO = new TplIncludeSaveReqVO();
                tplIncludeSaveReqVO.setIncludePage(dto.getIncludePage());
                tplIncludeSaveReqVO.setTplPath(dto.getFilename());
                tplIncludeSaveReqVO.setSiteId(site.getId());
                tplIncludeSaveReqVO.setUserId(SecurityFrameworkUtils.getLoginUserId());
                tplIncludeService.createTplInclude(tplIncludeSaveReqVO);
            }
        } else {
            //现在不是包含页面
            if (tplInclude != null) {
                TplIncludeSaveReqVO tplIncludeSaveReqVO = new TplIncludeSaveReqVO();
                tplIncludeSaveReqVO.setIncludePage(false);
                tplIncludeService.updateTplInclude(tplIncludeSaveReqVO);
            }
        }
    }

    /**
     * 删除文件
     *
     * @param nameDto 名称
     * @param result  BindingResult
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @Operation(summary = "模板删除")
    @PreAuthorize("@ss.hasPermission('cms:tpl:delete')")
    @PostMapping("/delete")
    public CommonResult<Boolean> delete(@Valid TplDeleteDto nameDto, BindingResult result) throws Exception {
        validateBindingResult(result);
        SiteDO site = CmsContextUtil.getSite();
        // 验证名字是否合法路径，文件是否存在不检查 可以直接文件夹
        for (String n : nameDto.getNames()) {
            //检查操作是否再指定目录下
            FileUtil.isValidFilename(site.getTplPath(), n);
        }
        tplService.delete(nameDto.getNames());
        return success(true);
    }


    /**
     * 修改模板名称
     *
     * @param dto     模板、资源重命名Dto
     * @param request HttpServletRequest
     * @param result  BindingResult
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @Operation(summary = "修改模板名称")
    @PreAuthorize("@ss.hasPermission('cms:tpl:update')")
    @PostMapping("/rename/update")
    public CommonResult<Boolean> rename(@Valid @RequestBody TplRenameDto dto, HttpServletRequest request,
                                        BindingResult result) throws ServerException {
        validateBindingResult(result);
        SiteDO site = CmsContextUtil.getSite();
        validReName(dto.getFileName(), dto.getNewName(), site.getTplPath());
        tplService.rename(dto.getFileName(), dto.getNewName());
        return success(true);
    }

    /**
     * 创建文件夹
     *
     * @param dto    创建文件夹Dto
     * @param result BindingResult
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @Operation(summary = "创建文件夹")
    @PreAuthorize("@ss.hasPermission('cms:tpl:save')")
    @PostMapping(value = "/dir")
    public CommonResult<Boolean> saveDir(@Valid @RequestBody TplSaveDirDto dto, BindingResult result) throws ServerException, IOException {
        validateBindingResult(result);
        SiteDO site = CmsContextUtil.getSite();
        String root = dto.getRoot();
        if (StringUtils.isBlank(root)) {
            root = site.getTplPath();
        }
        dto.setRoot(root);
        validSave(root + "/" + dto.getDirName(), root);
        if (StringUtils.isBlank(dto.getRoot())) {
            dto.setRoot(site.getTplPath());
        }
        tplService.saveDir(dto);
        return success(true);
    }

    /**
     * 校验文件是否唯一
     *
     * @param name 目录名称或者文件名称
     * @param root 目录路径
     * @return true 唯一 false 不唯一
     * @throws ServerException 异常
     */
    @Operation(summary = "校验文件是否唯一")
    @GetMapping(value = "/unique/checkByName")
    public CommonResult<Boolean> checkByName(String name, String root) throws ServerException {
        SiteDO site = CmsContextUtil.getSite();
        if (root == null) {
            root = site.getTplPath();
        }
        return success(!vldExistFile(root + SPT + name));
    }

    /**
     * 导入模板（zip）
     *
     * @param file    文件
     * @param request HttpServletRequest
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @Operation(summary = "导入模板")
    @PreAuthorize("@ss.hasPermission('cms:tpl:tplImport')")
    @PostMapping(value = "/import")
    public CommonResult<Boolean> tplImport(@RequestParam(value = "file", required = false) MultipartFile file,
                                           Boolean isCover, HttpServletRequest request) throws ServerException {
        validateFile(file);
        SiteDO site = CmsContextUtil.getSite();
        String filename = file.getOriginalFilename();
        filename = filename.substring(filename.lastIndexOf(".") + 1);
        if (!FileUtil.isValidZipExt(filename)) {
            throw exception(TPL_FILE_NOT_SUPPORT);
        }
        try {
            File tempFile = cn.hutool.core.io.FileUtil.createTempFile("zip", true);
            file.transferTo(tempFile);
            tplService.importZip(site, tempFile, isCover);
            tempFile.delete();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            throw exception(TPL_IMPORT_ERROR);
        }
        return success(true);
    }

    /**
     * 导出模板
     *
     * @param solution 模板方案
     * @param response HttpServletResponse
     * @throws ServerException 异常
     */
    @Operation(summary = "导出模板")
    @PreAuthorize("@ss.hasPermission('cms:tpl:export')")
    @GetMapping(value = "/export")
    public void export(String solution, Long siteId, HttpServletResponse response) throws ServerException {
        validBlank(solution);
        FileUtil.isValidFilename(null, solution);
        SiteDO site;
        if (siteId != null) {
            site = siteService.getSite(siteId);
        } else {
            site = CmsContextUtil.getSite();
        }
        try {
            List<Zipper.FileEntry> fileEntrys = tplService.export(site, solution);
            response.setContentType("application/x-download;charset=UTF-8");
            response.addHeader("Content-disposition", "filename=export-" + solution + ".zip");
            Zipper.zip(response.getOutputStream(), fileEntrys, "GBK");
        } catch (IOException e) {
            log.error("export template error!", e);
        }
    }

    /**
     * 上传模板
     *
     * @param root    路径
     * @param file    模板文件
     * @param isCover true 替换 false 保留
     * @param request HttpServletRequest
     * @return ResponseInfo
     * @throws ServerException 异常
     */
    @Operation(summary = "上传模板")
    @PreAuthorize("@ss.hasPermission('cms:tpl:create')")
    @PostMapping("/upload")
    public CommonResult<Boolean> uploadTemplate(@RequestParam(value = "file", required = false) MultipartFile file,
                                                String root, Boolean isCover, HttpServletRequest request) throws ServerException {
        Assert.notNull(file, "请选择文件");
        SiteDO site = CmsContextUtil.getSite();
        String filename = file.getOriginalFilename();
        filename = filename.substring(filename.lastIndexOf("."));
        //如果不是html文件则不让上传
        if (!CmsSysConstants.TPL_SUFFIX.equalsIgnoreCase(filename)) {
            throw exception(TPL_FILE_NOT_SUPPORT);
        }
        try {
            //判断该文件是否允许上传
            if (!FileUtil.checkFileIsValid(file.getInputStream(), true)) {
                throw exception(TPL_FILE_NOT_SUPPORT);
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            throw exception(TPL_UPLOAD_ERROR);
        }
        if (StringUtils.isBlank(root)) {
            root = site.getTplPath();
        } else {
            FileUtil.isValidFilename(site.getTplPath(), root);
        }
        validateFile(file);
        try {
            tplService.uploadTplResFile(root, file, isCover);
        } catch (IOException e) {
            log.error("上传模板失败{}", e);
            throw exception(TPL_UPLOAD_ERROR);
        }
        return success(true);
    }

    /**
     * 获取栏目和内容模板
     *
     * @param modelId  模型id 非必选，如果有则找到模型前缀的模板，否则是channel或者content下所有的模板
     * @param isDetail 是否内容模板 true 筛选 content 否则是 channel
     * @param pc       是否pc端
     * @return
     * @throws ServerException
     * @throws IOException
     * @throws ServiceException
     */
    @GetMapping(value = "/getChannelTpl")
    public CommonResult<List<String>> getChannelTpl(Integer modelId, Boolean isDetail, Boolean pc) throws ServerException, IOException, ServiceException {
        SiteDO site = CmsContextUtil.getSite();
        ModelDO model = null;
        if (modelId != null) {
            model = modelService.getModel(modelId);
            cn.hutool.core.lang.Assert.notNull(model, () -> {
                throw exception(MODEL_NOT_EXISTS);
            });
        }
        //模板路径
        String folder = "";
        if (model != null) {
            folder = EnumModelType.CHANNEL.getType().equals(model.getModelType()) ? TPL_CHANNEL : TPL_CONTENT;
        }
        if (isDetail != null) {
            folder = !isDetail ? TPL_CHANNEL : TPL_CONTENT;
        }
        String root;
        if (pc != null && pc) {
            root = site.getPcSolutionPath() + "/" + folder;
        } else {
            root = site.getH5SolutionPath() + "/" + folder;
        }
        List<IResFile> list = tplService.getChild(root);
        if (list == null || list.isEmpty()) {
            return success(Collections.emptyList());
        }
        if (model != null) {
            ModelDO finalModel = model;
            list = list.stream().filter(x -> x.getFilename().startsWith(finalModel.getTplPrefix())).collect(Collectors.toList());
        }
        List<String> tplNames = CollectionUtils.convertList(list, IResFile::getFilename);
        return success(tplNames);
    }

    /**
     * 获取首页模板
     *
     * @param pc 是否pc端模板
     * @return
     * @throws ServerException
     * @throws IOException
     * @throws ServiceException
     */
    @GetMapping(value = "/getIndexTpl")
    public CommonResult<List<String>> getIndexTpl(Boolean pc) throws ServerException, IOException, ServiceException {
        SiteDO site = CmsContextUtil.getSite();
        //模板路径
        String folder = TPL_INDEX;
        String root;
        if (pc != null && pc) {
            root = site.getPcSolutionPath() + "/" + folder;
        } else {
            root = site.getH5SolutionPath() + "/" + folder;
        }
        List<IResFile> list = tplService.getChild(root);
        if (list == null || list.isEmpty()) {
            return success(Collections.emptyList());
        }
        List<String> tplNames = CollectionUtils.convertList(list, IResFile::getFilename);
        return success(tplNames);
    }

    /**
     * 获取站点模板方案
     *
     * @return 站点模板方案文件夹
     * @throws ServerException
     * @throws IOException
     * @throws ServiceException
     */
    @GetMapping(value = "/getSolutions")
    @Operation(summary = "获取模板方案列表")
    public CommonResult<List<String>> getSolutions() throws ServerException, IOException, ServiceException {
        SiteDO site = CmsContextUtil.getSite();
        //模板路径
        String root = site.getTplPath();
        List<IResFile> list = tplService.getChild(root);
        if (list == null || list.isEmpty()) {
            return success(Collections.emptyList());
        }
        list = list.stream().filter(x -> x.isDirectory()).collect(Collectors.toList());
        return success(CollectionUtils.convertList(list, IResFile::getFilename));
    }

    @Resource
    private TplService tplService;
    @Resource
    private SiteService siteService;
    @Resource
    private ModelService modelService;
    @Resource
    private TplIncludeService tplIncludeService;
}
